#version 410

//#define DEBUG
#define RBGA_DEPTH

out vec3 out_Color;

uniform sampler2D u_depthTX;
uniform sampler2D u_normalTX;
uniform sampler2D u_diffuseTX;
uniform sampler2D u_miscTX;

uniform mat4 u_invPersp;
uniform mat4 u_View;

uniform int u_numLights;
uniform vec3 u_lightColor;
uniform vec3 u_lightPosition;
uniform float u_fadeFar;
uniform float u_cutoffFar;

in vec2 fs_Texcoord;

const float pointFactor = .8f * .4f;
const float spec_exp = 40.0f;

// Code from : http://aras-p.info/texts/CompactNormalStorage.html
vec3 unpackNormal(in vec2 encNorm) {
#ifdef DEBUG
	if(encNorm.x < .000001f && encNorm.y < .00000001)
		return vec3(.0f, .0f, .0f);
#endif

	encNorm = encNorm*4.0f - 2.0f;
	float f = dot(encNorm, encNorm);
	float g = sqrt(1.0f -.25f*f);
	return vec3(encNorm*g,1.0f - .5f*f);
}

vec4 packFloatTo4x8(in float val) {
	uint a = floatBitsToInt(val);
	return vec4((bitfieldExtract(a,0,8))/256.0f,
				(bitfieldExtract(a,8,8))/256.0f,
				(bitfieldExtract(a,16,8))/256.0f,
				(bitfieldExtract(a,24,8))/256.0f);
}

float unpack4x8ToFloat(in vec4 val) {
	uint a = uint(val.x*256.0f+.5f) + 
	uint(val.y*256.0f+.5f)*256u + 
	uint(val.z*256.0f+.5f)*256u*256u+
	uint(val.w*256.0f+.5f)*256u*256u*256u;
	return uintBitsToFloat(a);
}

// Packing and unpacking code courtesy of:
// http://www.ozone3d.net/blogs/lab/20080604/glsl-float-to-rgba8-encoder/
// http://olivers.posterous.com/linear-depth-in-glsl-for-real
vec4 pack2FloatTo4x8(in vec2 val) {
	const vec2 bitSh = vec2(256.0f, 1.0f);
	const vec2 bitMsk = vec2(0.0f, 1.0f/256.0f);
	vec2 res1 = fract(val.x * bitSh);
	res1 -= res1.xx * bitMsk;
	vec2 res2 = fract(val.y * bitSh);
	res2 -= res2.xx * bitMsk;
	return vec4(res1.x,res1.y,res2.x,res2.y);
}

vec2 unpack4x8To2Float(in vec4 val) {
	const vec2 unshift = vec2(1.0f/256.0f, 1.0f);
	return vec2(dot(val.xy, unshift), dot(val.zw, unshift));
}

float linearizeDepth(float exp_depth, float near, float far) {
    return	(2 * near) / (far + near -  exp_depth * (far - near)); 
}

vec3 lightCalc(in vec3 lPos, in vec3 fPos, in vec3 normal, in vec3 color) {
	float dist = distance(lPos, fPos);
	float attenuate = 1.0f;
	
	if(dist > u_cutoffFar)
		attenuate = 0.0f;
	else if(dist > u_fadeFar)
		attenuate -= (dist - u_fadeFar)/(u_cutoffFar - u_fadeFar);
	
	vec3 surfToLight = normalize(lPos - fPos);
	
	// -fPos is in the same direction as v
	vec3 h = normalize(surfToLight + normalize(-fPos));
	
	return attenuate*
		(color*(max(dot(surfToLight, normal), 0)));							// diffuse
		//+ .4f*vec3(1.0f, 1.0f, 1.0f)*pow(max(dot(h, normal), 0), spec_exp));	// specular
}

vec3 getVSPosition() {
	// Go from texture coordinate + depth to view space
#ifdef RBGA_DEPTH
	vec4 sPos = vec4(2.0f*fs_Texcoord.x - 1.0f, fs_Texcoord.y * 2.0f - 1.0f, 2.0*unpack4x8ToFloat(texture(u_depthTX, fs_Texcoord)) - 1.0f, 1.0f);
#else
	vec4 sPos = vec4(2.0f*fs_Texcoord.x - 1.0f, fs_Texcoord.y * 2.0f - 1.0f, 2.0*texture(u_depthTX, fs_Texcoord).x - 1.0f, 1.0f);
#endif
	sPos = u_invPersp * sPos;
	return sPos.xyz/sPos.w;
}

void main(void) {

	vec3 pos = getVSPosition();
	vec3 normal = unpackNormal(unpack4x8To2Float(texture(u_normalTX, fs_Texcoord)));
	
	vec3 color = lightCalc((u_View*vec4(u_lightPosition, 1.0f)).xyz, pos, normal, u_lightColor);

	out_Color = texture(u_diffuseTX, fs_Texcoord).xyz*color*pointFactor;
}